#pragma once

#include "deviceManager.hpp"
#include "msgObj.hpp"

namespace Protocol {
class DeviceReadStatMsgObject : public MsgObject {
    std::size_t tick;
    std::size_t time;
    std::string src;
    std::string dest;

    struct Ack {
        std::size_t tick;
        std::size_t time;
        friend void to_json(nlohmann::json &j, const Ack &a) { j = {{"tick", a.tick}, {"time", a.time}}; }
        Ack &setTickAndTime(std::size_t tick, std::size_t time) {
            this->tick = tick;
            this->time = time;
            return *this;
        }
    } ack;

    struct Dev {
        std::size_t id;
        std::string status;

        Dev &setStatus(bool isOnline) {
            status = isOnline ? "online" : "offline";
            return *this;
        }

        friend void from_json(const nlohmann::json &j, Dev &d) {
            if (j.contains("id")) {
                j.at("id").get_to(d.id);
            }
        }

        friend void to_json(nlohmann::json &j, const Dev &d) {
            j = {{"id", d.id}, {"status", d.status}};
        }
    };

    std::vector<Dev> dev;

public:
    DeviceReadStatMsgObject(std::shared_ptr<MqttClient> m, std::shared_ptr<DeviceManager> d)
        : MsgObject(m, d) {}

    friend void from_json(const nlohmann::json &j, DeviceReadStatMsgObject &d) {
        if (j.contains("tick")) {
            j.at("tick").get_to(d.tick);
        }

        if (j.contains("time")) {
            j.at("time").get_to(d.time);
        }

        if (j.contains("src")) {
            j.at("src").get_to(d.src);
        }

        if (j.contains("dev") && j.at("dev").is_array()) {
            for (const auto &dev : j.at("dev")) {
                d.dev.emplace_back(dev.get<Dev>());
            }
        }
    }

    friend void to_json(nlohmann::json &j, const DeviceReadStatMsgObject &d) {
        j = {{"tick", d.tick}, {"time", d.time}, {"src", d.src}, {"ack", d.ack}, {"dev", d.dev}};
    }

    virtual bool parse(const std::string &topic, const std::string &payload) override {
        auto dest = subTopic(topic, 4);
        if ((isDisit(dest) && !_device->findDevice(std::stoi(dest))) || (!isDisit(dest) && dest != selfSrc)) {
            return false;
        }
        this->dest = dest;

        nlohmann::json::parse(payload).get_to(*this);

        return true;
    }

    virtual MsgObject &action(void) override {
        for (auto &d : dev) {
            auto device = _device->findDevice(d.id);
            if (!device) {
                std::cerr << "can't find device, device id --> " << d.id << std::endl;
                d.setStatus(false);
            }

            d.setStatus(device->isOnline());
        }

        return *this;
    }

    virtual bool reply(void) override {
        ack.setTickAndTime(tick, time);
        auto topic = "v1/dev/" + dest + "/" + src + "/state/get_ack";
        src = selfSrc;

        return _mqtt->publish(topic, nlohmann::json(*this).dump());
    }
};

};  // namespace Protocol
